﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using EnvDTE;

namespace Lexware.Tools.ProjectPropertyOverview
{
    /// <summary>
    /// ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/dv_extvbcs/html/6323383a-43ee-4a60-be4e-9d7f0b53b168.htm
    /// </summary>
    internal class ProjectConfigurationProperties : IDataErrorInfo 
    {
        #region Private Members

        /// <summary>
        /// 
        /// </summary>
        private readonly Project _currentProject;
        /// <summary>
        /// 
        /// </summary>
        private readonly Properties _configurationProperties;
        /// <summary>
        /// 
        /// </summary>
        private readonly Properties _projectProperties;
        /// <summary>
        /// 
        /// </summary>
        private readonly Configuration _configuration;
        /// <summary>
        /// 
        /// </summary>
        private readonly string _projectName;

        #endregion

        #region Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="ProjectConfigurationProperties"/> class.
        /// </summary>
        /// <param name="projectName">Name of the project.</param>
        /// <param name="configuration">The configuration.</param>
        /// <param name="currentProject">The current project.</param>
        /// <exception cref="ArgumentNullException">If currentProject is null.</exception>
        public ProjectConfigurationProperties(string projectName, Configuration configuration, Project currentProject)
        {
            #region ArgumentNullExceptions
            if (currentProject == null)
            {
            	throw new ArgumentNullException("currentProject");
            }
            if (configuration == null)
            {
            	throw new ArgumentNullException("configuration");
            }
            #endregion

            _projectName = projectName;
            _configuration = configuration;
            _currentProject = currentProject;
            _configurationProperties = configuration.Properties;
            _projectProperties = currentProject.Properties;                       
        }
        #endregion

        #region Properties

        /// <summary>
        /// Gets the current project.
        /// </summary>
        /// <value>The current project.</value>
        internal Project CurrentProject
        {
            get
            {
                return _currentProject;
            }
        }

        /// <summary>
        /// Gets the name of the project file.
        /// </summary>
        /// <value>The name of the project file.</value>
        [Description("Project Filename"), Category("Application")]
        public string ProjectFileName
        {
            get { return _currentProject.FileName; }
        }

        /// <summary>
        /// Gets the name of the project file.
        /// </summary>
        /// <value>The name of the project file.</value>
        [Description("Configuration Name"), Category("Application")]
        public string ConfigurationName
        {
            get
            {
                try
                {
                    return _configuration.ConfigurationName;
                }
                catch
                {
                    return string.Empty;
                }
            }
        }

        /// <summary>
        /// Gets or sets the output path.
        /// </summary>
        /// <value>The output path.</value>
        private string ProjectFullPath
        {
            get 
            {
                if (_projectProperties != null)
                {
                    return (string)_projectProperties.Item("FullPath").Value;
                }
                return string.Empty;
            }
        }

        /// <summary>
        /// Gets or sets the output path.
        /// </summary>
        /// <value>The output path.</value>
        [Description("AssemblyName"), Category("Application")]
        public string AssemblyName
        {
            get
            {
                return GetProjectProperty<string>(ProjectProjectConfigurationConstants.Assemblyname);
            }
            set
            {
                SetProjectProperty(value, ProjectProjectConfigurationConstants.Assemblyname);
            }
        }

        /// <summary>
        /// Gets or sets the output path.
        /// </summary>
        /// <value>The output path.</value>
        [Description("Default Namespace"), Category("Application")]
        public string DefaultNamespace
        {
            get
            {
                return GetProjectProperty<string>(ProjectProjectConfigurationConstants.DefaultNamespace);
            }
            set
            {
                SetProjectProperty(value, ProjectProjectConfigurationConstants.DefaultNamespace);
            }
        }

        /// <summary>
        /// Gets or sets the warning level.
        /// </summary>
        /// <value>The warning level.</value>
        [Description("Errors and Warnings"), Category("Build")]
        public int WarningLevel
        {
            get
            {
                return GetConfigurationProperty<int>(ProjectProjectConfigurationConstants.WarningLevel);
            }
            set
            {
                SetConfigurationProperty(value, ProjectProjectConfigurationConstants.WarningLevel);
            }
        }

        /// <summary>
        /// Gets or sets the output path.
        /// </summary>
        /// <value>The output path.</value>
        [Description("Output Path"), Category("Build")]
        public string OutputPath
        {
            get
            {
                return GetConfigurationProperty<string>(ProjectProjectConfigurationConstants.Outputpath);
            }
            set
            {
                SetConfigurationProperty(value, ProjectProjectConfigurationConstants.Outputpath);
            }
        }

        /// <summary>
        /// Gets or sets the output path absolute
        /// </summary>
        /// <value>The output path.</value>
        [Description("Output Path (absolute)"), Category("Build")]
        public string OutputPathAbsolute
        {
            get
            {
                return ConvertToAbsolutePath(OutputPath);
            }
            set
            {
                OutputPath = ConvertToRelativePath(value);
            }
        }

        /// <summary>
        /// Gets or sets the documentation file.
        /// </summary>
        /// <value>The documentation file.</value>
        [Description("Documentation File"), Category("Build")]
        public string DocumentationFile
        {
            get
            {
                return GetConfigurationProperty<string>(ProjectProjectConfigurationConstants.DocumentationFile);
            }
            set
            {
                SetConfigurationProperty(value, ProjectProjectConfigurationConstants.DocumentationFile);
            }
        }

        /// <summary>
        /// Gets or sets the documentation file.
        /// </summary>
        /// <value>The documentation file.</value>
        [Description("Documentation File (absolute)"), Category("Build")]
        public string DocumentationFileAbsolute
        {
            get
            {
                return ConvertToAbsolutePath(DocumentationFile);
            }
            set
            {
                DocumentationFile = ConvertToRelativePath(value);
            }
        }

        /// <summary>
        /// Gets or sets the Code Analysis Rule Set.
        /// </summary>
        /// <value>The Code Analysis Rule Set.</value>
        [Description("Code Analysis Rule Set"), Category("Code Analysis")]              
        public string CodeAnalysisRuleSet
        {
            get
            {
                return GetConfigurationProperty<string>(ProjectProjectConfigurationConstants.CodeAnalysisRuleSet);
            }
            set
            {
                SetConfigurationProperty(value, ProjectProjectConfigurationConstants.CodeAnalysisRuleSet);
            }
        }

        /// <summary>
        /// Gets or sets the platform target.
        /// </summary>
        /// <value>The platform target.</value>
        [Description("Platform Target"), Category("Build")]
        public string PlatformTarget
        {
            get
            {
                return GetConfigurationProperty<string>(ProjectProjectConfigurationConstants.PlatformTarget);
            }
            set
            {
                SetConfigurationProperty(value, ProjectProjectConfigurationConstants.PlatformTarget);
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [treat warnings as errors].
        /// </summary>
        /// <value>
        /// 	<c>true</c> if [treat warnings as errors]; otherwise, <c>false</c>.
        /// </value>
        [Description("Treat warnings as errors"), Category("Build")]
        public bool TreatWarningsAsErrors
        {
            get
            {
                return GetConfigurationProperty<bool>(ProjectProjectConfigurationConstants.TreatwarningsAsErrors);
            }
            set
            {
                SetConfigurationProperty(value, ProjectProjectConfigurationConstants.TreatwarningsAsErrors);
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [run code analysis].
        /// </summary>
        /// <value><c>true</c> if [run code analysis]; otherwise, <c>false</c>.</value>
        [Description("Enable code analysis"), Category("Code Analysis")]
        public bool RunCodeAnalysis
        {
            get
            {
                return GetConfigurationProperty<bool>(ProjectProjectConfigurationConstants.RunCodeAnalysis);
            }
            set
            {
                SetConfigurationProperty(value, ProjectProjectConfigurationConstants.RunCodeAnalysis);
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [run code analysis].
        /// </summary>
        /// <value><c>true</c> if [run code analysis]; otherwise, <c>false</c>.</value>
        [Description("Status"), Category("Build")]
        public bool Status
        {
            get
            {
                return HasDefaultProperties();                
            }
        }

        ///// <summary>
        ///// Gets or sets the warning level.
        ///// </summary>
        ///// <value>The warning level.</value>
        //[Description("Code Analysis Rules"), Category("Code Analysis")]
        //public bool CodeAnalysisRules
        //{
        //    get
        //    {                
        //        return CheckCodeAnalysisRules(); 
        //    }
        //}

        /// <summary>
        /// 
        /// </summary>
        [Description("Project Name"), Category("Application")]
        public string ProjectName
        {
            get { return _projectName; }
        }

        #endregion Properties

        #region Public Methods
        /// <summary>
        /// 
        /// </summary>
        public void SetDefaultConfigurationProperties(IList<DefaultConfigurationPropertyItem> defaultProperties)
        {
            foreach (DefaultConfigurationPropertyItem defaultProperty in defaultProperties)
            {
                SetDefaultValueInProperty(defaultProperty);
            }           
        }

        #endregion Public Methods

        #region Private Methods

        ///// <summary>
        ///// Checks the code analysis rules.
        ///// </summary>
        ///// <returns></returns>
        //private bool CheckCodeAnalysisRules()
        //{
        //    string rules = (string)_configurationProperties.Item(ProjectProjectConfigurationConstants.CODE_ANALYSIS_RULES).Value;
        //    bool bReturn = false;
        //    IDictionary<string, DefaultConfigurationPropertyItem> dict = ProjectProjectConfigurationConstants.PredefinedConfigurationPropertiesAsDict;

        //    DefaultConfigurationPropertyItem item;
        //    if (dict.TryGetValue(ProjectProjectConfigurationConstants.CODE_ANALYSIS_RULES, out item))
        //    {
        //        if (((string)item.Value) == rules)
        //        {
        //            bReturn = true;
        //        }
        //    }

        //    return bReturn;
        //}

        /// <summary>
        /// Converts to absolute path.
        /// </summary>
        /// <param name="relativePath">The relative path.</param>
        /// <returns></returns>
        private string ConvertToAbsolutePath(string relativePath)
        {
            if (string.IsNullOrEmpty(relativePath))
            {
                return relativePath;
            }

            //if (Uri.IsWellFormedUriString(relativePath, UriKind.Relative))
            {
                Uri baseUri = new Uri(ProjectFullPath);
                Uri uriAbsolutePath = new Uri(baseUri, relativePath);
                return uriAbsolutePath.AbsolutePath.Replace('/', '\\');
            }
            //return relativePath;
        }

        /// <summary>
        /// Converts to relative path.
        /// </summary>
        /// <param name="absolutePath">The absolute path.</param>
        /// <returns></returns>
        private string ConvertToRelativePath(string absolutePath)
        {
            if (string.IsNullOrEmpty(absolutePath))
            {
                return absolutePath;
            }

            if (Uri.IsWellFormedUriString(absolutePath, UriKind.Absolute))
            {
                Uri uriAbsolutePath = new Uri(absolutePath);
                Uri containingProjectPathUri = new Uri(ProjectFullPath);
                Uri uriRelativeAssemblyPath = containingProjectPathUri.MakeRelativeUri(uriAbsolutePath);
                // Change the path in a way, visual studio would have created it.
                return uriRelativeAssemblyPath.ToString().Replace('/', '\\');

            }
            return absolutePath;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="defaultProperty"></param>
        private void SetDefaultValueInProperty(DefaultConfigurationPropertyItem defaultProperty)
        {
            string name = defaultProperty.Name;

            if (name == ProjectProjectConfigurationConstants.Outputpath)
            {
                OutputPath = (string)defaultProperty.Value;
            }
            else if (name == ProjectProjectConfigurationConstants.WarningLevel)
            {
                WarningLevel = Int32.Parse(defaultProperty.Value.ToString());
            }
            else if (name == ProjectProjectConfigurationConstants.TreatwarningsAsErrors)
            {
                TreatWarningsAsErrors = (bool)defaultProperty.Value;
            }
            else if (name == ProjectProjectConfigurationConstants.RunCodeAnalysis)
            {
                RunCodeAnalysis = (bool)defaultProperty.Value;
            }
            /*
            else if (name == ProjectProjectConfigurationConstants.DOCUMENTATION_FILE)
            {
                DocumentationFile = (string)defaultProperty.Value;
            }*/
            else if (name == ProjectProjectConfigurationConstants.PlatformTarget)
            {
                PlatformTarget = (string)defaultProperty.Value;
            }
            //else if (name == ProjectProjectConfigurationConstants.CODE_ANALYSIS_RULES)
            //{
            //    _configurationProperties.Item(ProjectProjectConfigurationConstants.CODE_ANALYSIS_RULES).Value = defaultProperty.Value;
            //}

        }


        /// <summary>
        /// Gets the project property.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="propertyName">Name of the property.</param>
        /// <returns></returns>
        private T GetProjectProperty<T>(string propertyName)
        {
            T value;
            try
            {
                Property property = _projectProperties.Item(propertyName);
                value = GetProperty<T>(property);
            }
            catch
            {
                value = default(T);
            }
            return value;
        }

        /// <summary>
        /// Gets the project property.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value">The value.</param>
        /// <param name="propertyName">Name of the property.</param>
        private void SetProjectProperty<T>(T value, string propertyName)
        {
            try
            {
                Property property = _projectProperties.Item(propertyName);
                SetProperty(value, property);
            }
            catch
            {
                // NOP
            }
        }

        /// <summary>
        /// Gets the project property.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="propertyName">Name of the property.</param>
        /// <returns></returns>
        private T GetConfigurationProperty<T>(string propertyName)
        {
            T value;
            try
            {
                Property property = _configurationProperties.Item(propertyName);
                value = GetProperty<T>(property);
            }
            catch (Exception)
            {
                value = default(T);
            }
            return value;
        }

        /// <summary>
        /// Gets the project property.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value">The value.</param>
        /// <param name="propertyName">Name of the property.</param>
        private void SetConfigurationProperty<T>(T value, string propertyName)
        {
            try
            {
                Property property = _configurationProperties.Item(propertyName);
                SetProperty(value, property);
            }
            catch
            {
                // NOP
            }
        }

        /// <summary>
        /// Gets the project property.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="property">The property.</param>
        /// <returns></returns>
        private static T GetProperty<T>(Property property)
        {
            return (property != null) ? (T)property.Value : default(T);
        }

        /// <summary>
        /// Gets the project property.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value">The value.</param>
        /// <param name="property">The property.</param>
        private static void SetProperty<T>(T value, Property property)
        {
            if (property != null)
            {
                property.Value = value;
            }
        }

        #endregion

        #region ErrorHandling

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public bool HasDefaultProperties()
        {
            IDictionary<string, DefaultConfigurationPropertyItem> defaultConfigurationProperties = ProjectProjectConfigurationConstants.PredefinedConfigurationPropertiesAsDict;
            DefaultConfigurationPropertyItem item; 

            #region Warning Level
            if (defaultConfigurationProperties.TryGetValue(ProjectProjectConfigurationConstants.WarningLevel, out item))
            {
                if (WarningLevel != Int32.Parse(item.Value.ToString()))
                {
                    return false; 
                }
            }
            #endregion

            #region Code Analysis Rules
            //if (defaultConfigurationProperties.TryGetValue(ProjectProjectConfigurationConstants.CODE_ANALYSIS_RULES, out item))
            //{
            //    string rules = (string)_configurationProperties.Item(ProjectProjectConfigurationConstants.CODE_ANALYSIS_RULES).Value;
                
            //    if (rules != (string)item.Value)
            //    {
            //        return false;
            //    }
            //}
            #endregion

            #region Platform Target
            if (defaultConfigurationProperties.TryGetValue(ProjectProjectConfigurationConstants.PlatformTarget, out item))
            {
                if (PlatformTarget != (string)item.Value)
                {
                    return false; 
                }
            }
            #endregion

            #region RUN_CODE_ANALYSIS
            if (defaultConfigurationProperties.TryGetValue(ProjectProjectConfigurationConstants.RunCodeAnalysis, out item))
            {

                if (RunCodeAnalysis != (bool)item.Value)
                {
                    return false;
                }
            }
            #endregion

            #region TREATWARNINGS_AS_ERRORS
            if (defaultConfigurationProperties.TryGetValue(ProjectProjectConfigurationConstants.TreatwarningsAsErrors, out item))
            {

                if (TreatWarningsAsErrors != (bool)item.Value)
                {
                    return false; 
                }
            }
            #endregion

            return true; 
        }


        private string _lastError = "";


        public string Error
        {
            get { return null; }//_lastError; }

        }

        public string this[string propertyName]
        {
            get
            {
                IDictionary<string, DefaultConfigurationPropertyItem> defaultConfigurationProperties = ProjectProjectConfigurationConstants.PredefinedConfigurationPropertiesAsDict;
                DefaultConfigurationPropertyItem item;

                switch (propertyName)
                {
                    case ProjectProjectConfigurationConstants.WarningLevel:
                        if (defaultConfigurationProperties.TryGetValue(ProjectProjectConfigurationConstants.WarningLevel, out item))
                        {
                            if (WarningLevel != Int32.Parse(item.Value.ToString()))
                            {
                                if (item.Value != null) return propertyName + " is not: " + Int32.Parse(item.Value.ToString());
                            }
                        }
                        break;

                    //case ProjectProjectConfigurationConstants.CODE_ANALYSIS_RULES:
                    //    if (defaultConfigurationProperties.TryGetValue(ProjectProjectConfigurationConstants.CODE_ANALYSIS_RULES, out item))
                    //    {
                    //        string rules = (string)_configurationProperties.Item(ProjectProjectConfigurationConstants.CODE_ANALYSIS_RULES).Value;

                    //        if (rules != (string)item.Value)
                    //        {
                    //            SetErrorInfo(info, "not default Code Anlysis Rules" , ErrorType.Warning);
                    //        }
                    //    }
                    //    break;

                    case ProjectProjectConfigurationConstants.PlatformTarget:
                        if (defaultConfigurationProperties.TryGetValue(ProjectProjectConfigurationConstants.PlatformTarget, out item))
                        {

                            if (PlatformTarget != (string)item.Value)
                            {
                                return propertyName + " is not: " + (string)item.Value;
                            }
                        }
                        break;

                    case ProjectProjectConfigurationConstants.RunCodeAnalysis:
                        if (defaultConfigurationProperties.TryGetValue(ProjectProjectConfigurationConstants.RunCodeAnalysis, out item))
                        {

                            if (RunCodeAnalysis != (bool)item.Value)
                            {
                                return propertyName + " is not: " + (bool)item.Value;
                            }
                        }
                        break;

                    case ProjectProjectConfigurationConstants.TreatwarningsAsErrors:
                        if (defaultConfigurationProperties.TryGetValue(ProjectProjectConfigurationConstants.TreatwarningsAsErrors, out item))
                        {

                            if (TreatWarningsAsErrors != (bool)item.Value)
                            {                                
                                return propertyName + " is not: " + (bool)item.Value;
                            }
                        }
                        break;

                    case "Status":
                        if (!HasDefaultProperties())
                        {
                            return "Error";
                        }
                        break;

                    default: _lastError = "";
                        break;

                }
                return _lastError;

            }
        }
        #endregion ErrorHandling
    }
}
